#!coding:utf-8
# vim: tabstop=4 shiftwidth=4 softtabstop=4
'''
使用netfilter

1.出入都可以限制
2.对协议支持非常全面
3.和ip无关，和网卡绑定
4.用户设置静态ip不能绕过安全限制

限制范围
nat网络和隔离网络,bridge  :  host-vm,vm-vm,vm-host

example:

<filter name='test-eth0' chain='ipv4'>
  <uuid>7886f647-75ec-d104-0629-a55d23242d46</uuid>
  <filterref filter='clean-traffic'/>
  <filterref filter='allow-dhcp'/>
  <rule action='accept' direction='in' priority='500'>
    <tcp dstportstart='22'/>
  </rule>
  <rule action='accept' direction='in' priority='500'>
    <tcp dstportstart='8000'/>
  </rule>
  <rule action='accept' direction='out' priority='500'>
    <all/>
  </rule>
  <rule action='drop' direction='inout' priority='500'>
    <all/>
  </rule>
</filter>

filter chain:
    root
    mac (since 0.9.8)
    stp (spanning tree protocol) (since 0.9.8)
    vlan (802.1Q) (since 0.9.8)
    arp, rarp
    ipv4
    ipv6

filter支持的协议:
mac
vlan
stp
arp
ip
ipv6
tcp/udp/sctp
icmp
igmp, esp, ah, udplite
all
---------------------------------------
tcp-ipv6, udp-ipv6, sctp-ipv6
icmpv6
igmp-ipv6, esp-ipv6, ah-ipv6, udplite-ipv6
all-ipv6

------------------------------------------
filter 可以动态改变，所以我们的方式是每个虚拟机生成时绑定一个空的filter，之后对filter的改变可以及时应用到vm
但是要注意的是chain的改变需要重新启动虚拟机（hipervisor 重启)


'''
from lxml import etree
from cvirt.cnode.libvirtXmlObject.baseDomain import XmlObject
from cvirt.common import exception


class NetworkFilter(XmlObject):
    ROOT_NAME = 'filter'
    RNG_NAME = 'nwfilter'
    def __init__(self, parse_xml=None, **kw):
        self.name = None
        self.chain = None
        self.uuid = None
        self.refs = []
        self.rules = []

        super(NetworkFilter, self).__init__(parse_xml=parse_xml, **kw)



    # def _validate(self):
    #     if not self.name:
    #         raise exception.Error('filter xml must have a name')

        # if xmldoc.find('./uuid') is None:
        #     raise exception.Error('filter xml must have uuid')

    def _parse_xml(self, xmldoc):
        '''<filter name="test-filter" chain="ipv4">
        <filterref filter='allow-dhcp'/>
          <rule priority="500" action="accept" direction="in">
            <tcp dstportstart="20" dstportend="40"/>
          </rule>
        <rule priority="1500" action="drop" direction="out">
            <icmp/>
          </rule>
        </filter>
        '''
        self.name = xmldoc.get('name')
        if xmldoc.find('./uuid') is not None:
            self.uuid = xmldoc.find('./uuid').text
        self.chain = xmldoc.get('chain') if xmldoc.get('chain') else "ipv4"
        for c in xmldoc.getchildren():
            if c.tag == 'filterref' and c.get('filter'):
                self.refs.append(c.get('filter'))
            if c.tag == 'rule':
                self.rules.append(FilterRules(parse_xml=etree.tostring(c)))

    def _get_xml_config(self, root):

        root.set('name', self.name)
        if self.uuid:
            uuid = etree.Element('uuid')
            uuid.text = self.uuid
            root.append(uuid)
        if self.chain:
            root.set('chain', self.chain)
        for rule in self.rules:
            root.append(rule.get_xml_config())
        for ref in self.refs:
            root.append(etree.Element("filterref", filter=ref))
        return root


class FilterRules(XmlObject):
    ROOT_NAME = 'rule'

    def __init__(self, parse_xml=None, action=None, direction=None, priority=None,ip_rules=[] ,**kw):
        self.action = action
        self.direction = direction
        self.priority = priority if priority else "500"
        self.ip_rules = ip_rules
        super(FilterRules, self).__init__(parse_xml=parse_xml, **kw)

    # def _validate(self):
    #     if not self.action:
    #         raise exception.Error('rule must have *action* attritte')
    #     if not self.direction:
    #         raise exception.Error('rule must have *direction* direction')


    def _parse_xml(self, xmldoc):
        '''
        <rule priority="500" action="accept" direction="in">
            <tcp dstportstart="20" dstportend="40"/>
          </rule>
        @param xmldoc:
        @return:
        '''
        self.priority = xmldoc.get('priority') if xmldoc.get('priority') else "500"
        self.action = xmldoc.get('action')
        self.direction = xmldoc.get('direction')
        for p in xmldoc.getchildren():
            self.ip_rules.append(Rule(protocal=p.tag, parse_xml=etree.tostring(p)))

    def _get_xml_config(self, root):
        root.set('priority',self.priority)
        root.set('action',self.action)

        root.set('direction',self.direction)

        for ip_rule in self.ip_rules:
            root.append(ip_rule.get_xml_config())

        return root


class Rule(XmlObject):
    ROOT_NAME = 'all'  #['tcp','udp','icmp','all']

    def __init__(self, parse_xml=None,
                 protocal=None,
                 srcipfrom=None,
                 srcipto=None,
                 srcipaddr=None,
                 dstipfrom=None,
                 dstipto=None,
                 dstipaddr=None,
                 srcmacaddr=None,
                 srcportstart=None,
                 srcportend=None,
                 dstportstart=None,
                 dstportend=None,
                 srcipmask=None,
                 dstipmask=None, **kw):
        """
            IP rule
            srcmacaddr	MAC_ADDR	MAC address of sender
            srcmacmask	MAC_MASK	Mask applied to MAC address of sender
            dstmacaddr	MAC_ADDR	MAC address of destination
            dstmacmask	MAC_MASK	Mask applied to MAC address of destination
            srcipaddr	IP_ADDR	Source IP address
            srcipmask	IP_MASK	Mask applied to source IP address
            dstipaddr	IP_ADDR	Destination IP address
            dstipmask	IP_MASK	Mask applied to destination IP address
            protocol	UINT8, STRING	Layer 4 protocol identifier
            srcportstart	UINT16	Start of range of valid source ports; requires protocol
            srcportend	UINT16	End of range of valid source ports; requires protocol
            dstportstart	UINT16	Start of range of valid destination ports; requires protocol
            dstportend
        @param parse_xml:
        """


        self.protocal = protocal
        self.srcipfrom = srcipfrom
        self.srcipto = srcipto
        self.srcipaddr = srcipaddr
        self.dstipfrom = dstipfrom
        self.dstipto = dstipto
        self.dstipaddr = dstipaddr
        self.srcmacaddr = srcmacaddr
        self.srcportstart = srcportstart
        self.srcportend = srcportend
        self.dstportstart = dstportstart
        self.dstportend = dstportend
        self.srcipmask = srcipmask
        self.dstipmask = dstipmask
        super(Rule, self).__init__(parse_xml=parse_xml, **kw)


    def get_protocal(self):
        return self.ROOT_NAME

    def set_protocal(self,protocal):
        self.ROOT_NAME = protocal

    protocal = property(get_protocal,set_protocal)
    #
    # def _validate(self):
    #     if self.protocal not in ['tcp', 'udp', 'icmp', 'all']:
    #         raise exception.Error("filter rule only support for protocal: ['tcp','udp','icmp','all']")

    def _parse_xml(self, xmldoc):
        self.protocal = xmldoc.tag
        self.srcipfrom = xmldoc.get('srcipfrom')
        self.srcipto = xmldoc.get('srcipto')
        self.srcipaddr = xmldoc.get('srcipaddr')
        self.dstipfrom = xmldoc.get('dstipfrom')
        self.dstipto = xmldoc.get('dstipto')
        self.dstipaddr = xmldoc.get('dstipaddr')
        self.srcmacaddr = xmldoc.get('srcmacaddr')
        self.srcportstart = xmldoc.get('srcportstart')
        self.srcportend = xmldoc.get('srcportend')
        self.dstportstart = xmldoc.get('dstportstart')
        self.dstportend = xmldoc.get('dstportend')
        self.srcipmask = xmldoc.get('srcipmask')
        self.dstipmask = xmldoc.get('dstipmask')

    def _get_xml_config(self, root):
        if self.srcipfrom:
            root.set('srcipfrom', self.srcipfrom)
        if self.srcipto:
            root.set('srcipto', self.srcipto)
        if self.srcipaddr:
            root.set('srcipaddr', self.srcipaddr)
        if self.dstipfrom:
            root.set('dstipfrom', self.dstipfrom)

        if self.dstipto:
            root.set('dstipto', self.dstipto)
        if self.dstipaddr:
            root.set('dstipaddr', self.dstipaddr)
        if self.srcmacaddr:
            root.set('srcmacaddr', self.srcmacaddr)
        if self.srcportstart:
            root.set('srcportstart', self.srcportstart)

        if self.srcportend:
            root.set('srcportend', self.srcportend)
        if self.dstportstart:
            root.set('dstportstart', self.dstportstart)
        if self.dstportend:
            root.set('dstportend', self.dstportend)
        if self.srcipmask:
            root.set('srcipmask', self.srcipmask)

        if self.dstipmask:
            root.set('dstipmask', self.dstipmask)

        return root